/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun<lingyun@email.com>
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(06/06/19)
 *         Author:  LingYun <lingyun@email.com>
 *      ChangeLog:  1, Release initial version on "06/06/19 18:16:21"
 *                 
 ********************************************************************************/
#include "comport.h"


/*  初始化串口结构体 */
st_comport *comport_init(const char *dev_name,long baudrate, int databit, const char parity, int stopbit,const char flowctrl)
{
    st_comport *comport = NULL;
    if (NULL == (comport = (st_comport *) malloc(sizeof(st_comport))))
    {
        return NULL;
    }
    memset(comport, 0, sizeof(st_comport));
    comport->used = 0;

    strncpy(comport->dev_name, dev_name, DEVNAME_LEN); //串口设备名字
    comport->baudrate = baudrate; //波特率
    comport->databit = databit ; //数据位
    comport->parity = parity ; //校验位
    comport->stopbit = stopbit ; //停止位
    comport->flowctrl = flowctrl ; //流控
    comport->frag_size = 128 ;

    printf("TTY Parameter [baudrate: %ld, databit: %d, parity: %c, stopbit: %d, flowctrl: %c]\n",  comport->baudrate,\
            comport->databit,comport->parity,comport->stopbit, comport->flowctrl ) ; 

    return comport;
}


/*  清理串口结构体 */
void comport_term(st_comport * comport)
{
    if(NULL == comport)
        return;

    if((tcsetattr(comport->fd, TCSANOW,&(comport->original_opt)))!=0) //设置回原来的串口属性
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        return ;
    }

    if ( comport->fd != 0)
    {
        comport_close(comport);
    }
    memset(comport, 0x00, sizeof(st_comport)); 
    free(comport);
    comport = NULL;
    return;
}


/*    打开串口     */
int open_comport(st_comport * comport) 
{
    int     rv = -1 ;
    struct termios options;

    if(comport->fd > 0)
    {
        printf("com port already open!\n") ;
        return -1 ;
    }
    comport->fd = open(comport->dev_name,O_RDWR|O_NOCTTY|O_NONBLOCK) ; //打开串口设备
    if(comport->fd < 0)
    {
        printf("open tty failed:%s\n", strerror(errno)) ;
        return -1 ;
    }
    printf("open devices sucessful!\n") ;
    if( !strstr(comport->dev_name, "tty")) //打开的设备不是串口设备
    {
        printf("Open Not tty device \"%s\"\n", comport->dev_name);
        comport->fd = open(comport->dev_name, O_RDWR);
        rv = comport->fd<0 ? -2 : comport->fd;
        return rv;
    }

    rv = tcgetattr(comport->fd, &(comport->original_opt)); //获取原有的串口属性的配置
    if(rv != 0)
    {
        printf("tcgetattr() get original options failed:%s\n",strerror(errno)) ;
        return -3 ;
    }
    
    rv = tcgetattr(comport->fd, &options); //获取原有的串口属性的配置
    if(rv != 0)
    {
        printf("tcgetattr() failed:%s\n",strerror(errno)) ;
        return -4 ;
    }

    options.c_cflag &= ~CSIZE;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    options.c_oflag &= ~(OPOST);



    set_baudrate(&options, comport->baudrate) ; //设置波特率
    /*  8N1N */
    set_data_bit(&options, comport->databit) ;//设置数据位
    set_parity(&options, comport->parity) ; //设置校验位
    set_stopbit(&options, comport->stopbit) ; //设置停止位
    set_flowctrl(&options,comport->flowctrl) ; //设置流控


    /*   Set the Com port timeout settings */
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 0;

    tcflush(comport->fd, TCIFLUSH); //清空缓存区

    if((tcsetattr(comport->fd, TCSANOW,&options))!=0) //设置串口属性
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        return -5;
    }
    comport->used = 0x01 ;

    return comport->fd ;
}

/*  关闭串口 */
void comport_close(st_comport * comport)
{
    if (comport->fd != 0)
    {
        printf("Close device \"%s\"\n", comport->dev_name);
        close(comport->fd);
    }
    comport->used = 0x00;
    comport->fd = -1;
}


/*  设置波特率 */
void set_baudrate(struct termios *opt, int baudrate)
{
    /*  2400 4800 9600 19200 38400 57600 115200 */
    switch(baudrate)
    {
        case 2400:
            _set_baudrate(opt, B2400);
            break;
        case 4800:
            _set_baudrate(opt, B4800);
            break;
        case 9600:
            _set_baudrate(opt, B9600);
            break;
        case 19200:
            _set_baudrate(opt, B19200);
            break;
        case 38400:
            _set_baudrate(opt, B38400);
            break;
        case 57600:
            _set_baudrate(opt, B57600);
            break;
        case 115200:
            _set_baudrate(opt, B115200);
            break;
        default:
            _set_baudrate(opt, B115200);
            break;
    }
}
int _set_baudrate(struct termios *opt,speed_t baudrate)
{
    if(cfsetispeed(opt,baudrate) < 0)
    {
        printf("cfsetispeed failure:%s\n",strerror(errno));
        return -1;
    }
    if(cfsetospeed(opt,baudrate) < 0)
    {
        printf("cfsetospeed failure:%s\n",strerror(errno));
        return -2;
    }
    return 0 ;
}

/*  设置停止位 */
void set_stopbit (struct termios *opt, int stopbit)
{
    switch(stopbit)
    {
        case 2:
            opt->c_cflag |= CSTOPB;                                                            /*   2 位停止位             */
            break ;
        default:
            opt->c_cflag &= ~CSTOPB;                                                          /*   1 位停止位             */
            break ;
    }
}


/*  设置数据位 */
void set_data_bit (struct termios *opt, int databit)
{
    opt->c_cflag|=(CLOCAL|CREAD ); // CREAD 开启串行数据接收，CLOCAL并打开本地连接模式
    opt->c_cflag &= ~CSIZE;
    switch (databit) {
        case 8:
            opt->c_cflag |= CS8;
            break;
        case 7:
            opt->c_cflag |= CS7;
            break;
        case 6:
            opt->c_cflag |= CS6;
            break;
        case 5:
            opt->c_cflag |= CS5;
            break;
        default:
            opt->c_cflag |= CS8;
            break;
    }
}

/*   设置校验位    */
void set_parity (struct termios *opt,const char parity)
{
    switch (parity) {
        case 'N':                                                                                   /*   无校验          */
        case 'n':
            opt->c_cflag &= ~PARENB;
            break;
        
        case 'E':                                                                                   /*   偶校验          */
        case 'e':
            opt->c_cflag |= PARENB;
            opt->c_cflag &= ~PARODD;;
            opt->c_cflag |= (INPCK | ISTRIP);
            break;

        case 'O':                                                                                   /*   奇校验           */
        case 'o':
            opt->c_cflag |= (PARENB | PARODD);
            opt->c_cflag |= (INPCK | ISTRIP);
            break;

        default:                                                                                    /*   其它选择为无校验 */
            opt->c_cflag &= ~PARENB;
            break;
    }
}


/*  设置流控 */
void set_flowctrl(struct termios *opt,const char flowctrl)
{
    switch (flowctrl)
    {
        case 'S':                       // Software control
        case 's':
        case 'B':
        case 'b':
            opt->c_cflag &= ~(CRTSCTS);
            opt->c_iflag |= (IXON | IXOFF);
            break;
        case 'H':                       // Hardware control
        case 'h':
            opt->c_cflag |= CRTSCTS;   // Also called CRTSCTS
            opt->c_iflag &= ~(IXON | IXOFF);
            break;
        default:                 // NONE
            opt->c_cflag &= ~(CRTSCTS);
            opt->c_iflag |= (IXON|IXOFF);
            break;
    }
}
/*  写串口 */
int write_comport(st_comport *comport, char *buf, int buf_size) 
{
    char *ptr, *end;
    int     rv = 0 ;
    int     send = 0 ;
    if((!buf) || (buf_size<=0))
    {
        printf("%s Invailed parameter!\n", __FUNCTION__) ;
        rv = -1 ;
        goto cleanup ;
    }
    if (0x01 != comport->used)    // Comport not opened ?
    {
        printf("Serail not connected.\n");
        rv = -2 ;
        goto cleanup ;
    }
    if (comport->frag_size < buf_size)
    {
        ptr = buf;
        end = buf + buf_size;
        do
        {
            if (comport->frag_size < (end - ptr))
            {
                send = write(comport->fd, ptr, comport->frag_size);
                if (0 >= send || comport->frag_size != send)
                {
                    rv = -4;
                    goto cleanup;
                }
                ptr += comport->frag_size;
            }
            else
            {
                send = write(comport->fd, ptr, (end - ptr));
                if (0 >= send || (end - ptr) != send)
                {
                    rv = -4;
                    goto cleanup;
                }
                ptr += (end - ptr);
            }
        }while (ptr < end);
    }
    else{
        send = write(comport->fd, buf, buf_size);
        if (0 >= send || buf_size != send)
        {
            rv = -5;
            goto cleanup;
        }
    }
cleanup:
    return rv;
}

/*  读串口 */
int read_comport(st_comport *comport, char *buf, int buf_size, int timeout)
{
    fd_set                  rset ;
    struct timeval          tim_out; 
    int                     rv;

    if((!buf)||(buf_size<= 0))
    {
        printf("Invaild input\n") ;
        return -1 ;
    }
    if (0x01 != comport->used)    // Comport not opened ?
    {
        printf("Serail not connected.\n");
        return -2 ;
    }
    if(timeout) //如果传的参数timeout不为0,用多路复用定时超时时间
    {
        tim_out.tv_sec = (time_t) (timeout / 1000);
        tim_out.tv_usec = (long)(1000 * (timeout % 1000)); 
        FD_ZERO(&rset) ;
        FD_SET(comport->fd, &rset) ;
        rv = select(comport->fd+1, &rset, NULL, NULL, &tim_out) ;
        if(rv < 0) //select error.
        {
            printf("select() failed: %s\n", strerror(errno)) ;
            return -3 ;
        }
        if(rv== 0) //select time out.
        {
            return TIMEOUT ;
        }
    }
    usleep(10000); /*  sleep for 10ms for data incoming  */
    rv = read(comport->fd, buf, buf_size) ;
    if(rv <= 0)//read from client failed 
    {
        printf("Read from buf error or disconnect:%s\n", strerror(errno)) ;
        return -5 ;
    }
    return rv ;
}
